package quarto.model;

import java.util.ArrayList;


/**
 * Stelt het speelbord voor. 
 * Houd bij welk stuk op welk vakje staat en welke stukken nog
 * beschikbaar zijn
 * @knows
 * - De beschikbare stukken
 * - Inhoud vakjes
 * - Of er een winnaar is
 * - Of het bord vol is
 * @does
 * - Vult de vakjes
 * @author Frank Borgonjen & Tim Smit
 */
public class Board
{
    /** het aantal kolommen en rijen */
	public static final int DIM = 4;
    
    /**
     * Geeft een textuele representatie
     * @return het bord
     */
    @Override
    public String toString()
    {
        String fields = "";
        
        String tmp;
        int i = 0;
        while (i < DIM * DIM)
        {
            // altijd twee tekens voor een piece (04, 05, 12 etc) of -- voor null
            tmp = (this.getPiece(i) == null) ? "--" : "" + this.getPiece(i);
            if (tmp.length() == 1)
            {
                tmp = "0" + tmp;
            }
            
            tmp += ((i + 1) % 4 == 0) ? "\n" : "|";
            fields += tmp;
            
            i++;
        }
        
        return fields;
    }
    
    /**
     * Alle beschikbare stukken
     */
    private ArrayList<Piece> pieces;
    
    /**
     * Alle velden op het bord
     */
    private ArrayList<Piece> board;
    
    /**
     * De winnende vakjes
     */
    private ArrayList<Integer> winningFields;
    
    /**
     * constructor van het bord
     */
    public Board()
    {
        this.board = new ArrayList<Piece>();
        reset();
    }
    
    /**
     * Reset het board <br />
     * - maakt DIM*DIM unieke stukken aan<br />
     * - maakt een speelveld van DIM*DIM vakjes, die allemaal leeg zijn
     */
    public void reset()
    {
    	this.pieces = new ArrayList<Piece>();
        
        // lus door de verschillende eigenschappen heen
        int i = 0;
        Piece p;
        while (i < DIM * DIM)
        {
            try
            {
                p = new Piece(i++);
                this.addPiece(p);
            }
            catch (InvalidPieceValueException e) {}
        }
        
        // zet alle vakjes van het board op null
        i = 0;
        while (i < DIM * DIM)
        {
            this.board.add(i, null);
            i++;
        }
    }
    
    /**
     * Geeft een kopie van het bord
     * @return
     */
    public Board deepCopy()
    {
        Board newBoard = new Board();
        int i = 0;
        
        // lussen door alle velden
        while (i < DIM * DIM)
        {
            newBoard.setPiece(i, this.getPiece(i));
            i++;
        }
        
        return newBoard;
    }
    
    /**
     * Geeft de index terug van het opgevraagde stuk
     * @param int row
     * @param int colum
     * @return int index
     */
    public int getIndex(int row, int column)
    {
        return row * DIM + column;
    }

    /**
     * Geeft het stuk terug op de meegegeven index
     * @param index
     * @require 0 <= index < DIM*DIM
     * @return Piece het opgevraagde stuk
     */
    public Piece getPiece(int index)
    {
        return this.board.get(index);
    }
    
    /**
     * Geeft aan of het vakje op de meegegeven index leeg is.
     * @require 0 <= index < DIM*DIM
     * @param index de index van het vakje
     * @return true als het vakje leeg is, anders false
     */
    public boolean isEmpty(int index)
    {
        return this.board.get(index) == null;
    }
    
    /**
     * Kijkt of het meegegeven stuk nog voorkomt in de lijst
     * met beschikbare stukken
     * @param  piece het stuk waarvan we willen weten of het nog beschikbaar is
     * @return true als het stuk beschikbaar is, andere false
     */
    public boolean isAvailablePiece(Piece piece)
    {
        boolean available = false;
        int i = 0;
        
        // lussen zolang er niks gevonden is
        while (i < this.pieces.size() && !available)
        {
            if (this.pieces.get(i).compare(piece))
            {
                available = true;
            }
            
            i++;
        }
        
        return available;
    }
    
    /**
     * Geeft een lijst met beschikbare pieces terug
     * @return lijst met beschikbare pieces
     */
    public ArrayList<Piece> getAvailablePieces()
    {
        return this.pieces;
    }
    
    /**
     * Voegt een stuk toe aan de arraylist
     * @param p het stuk dat toegevoegd wordt
     */
    public void addPiece(Piece p)
    {
        this.pieces.add(p);
    }
    
    /*
     * Controle methoden
     * Er wordt gecontroleerd op de verscheidene mogelijkheden
     * om een quarto te hebben.
     * Een quarto houdt in dat je een rij, kolom, diagonaal of
     * een 2x2-vierkant hebt die 1 of meer dezelfde eigenschappen
     * hebben.
     */
    
    /**
     * Controleert of er een quarto bestaat op de meegegeven vakjes
     * @param fieldOne
     * @param fieldTwo
     * @param fieldThree
     * @param fieldFour
     * @return boolean is er een quarto op de meegegeven vakjes
     */
    public boolean checkQuarto(
        int fieldOne,
        int fieldTwo,
        int fieldThree,
        int fieldFour)
    {
        boolean hasQuarto = false;
        
        Piece pieceOne   = this.getPiece(fieldOne);
        Piece pieceTwo   = this.getPiece(fieldTwo);
        Piece pieceThree = this.getPiece(fieldThree);
        Piece pieceFour  = this.getPiece(fieldFour);
        
        if (pieceOne   != null 
         && pieceTwo   != null
         && pieceThree != null
         && pieceFour  != null)
        {
            hasQuarto = (pieceOne.toDecimal()
                & pieceTwo.toDecimal()
                & pieceThree.toDecimal()
                & pieceFour.toDecimal()) > 0;
            
            int mask = 1;
            while (!hasQuarto && mask < 16)
            {
                hasQuarto = ((pieceOne.toDecimal()
                    | pieceTwo.toDecimal()
                    | pieceThree.toDecimal()
                    | pieceFour.toDecimal())
                    & mask) == 0;
                mask *= 2;
            }
        }
        
        if (hasQuarto)
        {
            this.winningFields = new ArrayList<Integer>(4);
            this.winningFields.add(fieldOne);
            this.winningFields.add(fieldTwo);
            this.winningFields.add(fieldThree);
            this.winningFields.add(fieldFour);
        }
        
        return hasQuarto;
    }
    
    /**
     * Controleert of er een Quartorij is
     * @return boolean quartorij
     */
    public boolean hasRow()
    {
        boolean hasRow = false;
        int i = 0;

        while (i < DIM*DIM && !hasRow)
        {
            hasRow = this.checkQuarto(i++, i++, i++, i++);
        }

        return hasRow;
    }
    
    /**
     * Controleert of er een Quartokolom is
     * @return boolean quartokolom
     */
    public boolean hasColumn()
    {
        boolean hasColumn = false;
        int i = 0;

        // door de kolommen lussen
        while (i < DIM && !hasColumn)
        {
            hasColumn = this.checkQuarto(
                i, 
            	i + DIM, 
            	i + (2*DIM), 
            	i + (3*DIM));
            
            i++;
        }

        return hasColumn;
    }
    
    /**
     * Controleert of er een Quartodiagonaal is
     * @return boolean quartodiagonaal
     */
    public boolean hasDiagonal()
    {
        return this.checkQuarto(0, 5, 10, 15)
            || this.checkQuarto(3, 6, 9, 12);
    }
    
    /**
     * Controleert of er een Quartovierkant is
     * @return boolean quartovierkant
     */
    public boolean hasQuarto()
    {
        boolean hasQuarto = false;
        int rows = 0;
        
        // door de rijen lussen
        while (rows < DIM - 1 && !hasQuarto)
        {
            int columns = 0;
            
            // door de kolommen lussen
            while (columns < DIM - 1 && !hasQuarto)
            {
                hasQuarto = this.checkQuarto(
                    this.getIndex(rows, columns),
                    this.getIndex(rows + 1, columns),
                    this.getIndex(rows, columns + 1),
                    this.getIndex(rows + 1, columns + 1));

                columns++;
            }

            rows++;
        }

        return hasQuarto;
    }
    
    /*
     * Einde controlemethoden
     */
    
    /**
     * Geeft terug of het potje is afgelopen
     * @return booleaon spel voorbij
     */
    public boolean gameOver()
    {
        return this.hasWinner()
            || this.isFull();
    }

    /**
     * Is er een winnaar
     * @return boolean winnaar
     */
    public boolean hasWinner()
    {
        // controleren op alle mogelijke manieren om te winnen
        return this.hasRow()
            || this.hasColumn()
            || this.hasDiagonal()
            || this.hasQuarto();
    }

    /**
     * Is het speelveld vol
     * @return boolean vol
     */
    public boolean isFull()
    {
        boolean isFull = true;
        int i = 0;

        // door het speelveld heen lussen
        while (i < DIM * DIM && isFull)
        {
            isFull = this.getPiece(i) != null;

            i++;
        }

        return isFull;
    }
    
    /**
     * Geeft de winnende vakjes terug
     * @return de winnende vakjes
     */
    public ArrayList<Integer> getWinningFields()
    {
        return this.winningFields;
    }
    
    /**
     * Is de locatie een geldige, lege locatie op het speelveld
     * @param locatie
     * @return geldig && leeg
     */
    public boolean validAvailableLocation(int location)
    {
         return location >= 0 && location < DIM * DIM && this.isEmpty(location);
    }
    
    /**
     * Zet een bepaald stuk op een bepaalde plek
     * @param index de index van het vakje waar we de piece willen zetten
     * @param piece het stuk dat op het vakje gezet moet worden
     */
    public void setPiece(int index, Piece piece)
    {
        this.board.set(index, piece);
        
        if (piece != null)
        {
            // piece verwijderen uit beschikbare pieces
            boolean found = false;
            int i = 0;
            Piece p = null;
            while (!found && i < pieces.size())
            {
                p = pieces.get(i);
                if (p.compare(piece))
                {
                    found = true;
                    this.pieces.remove(i);
                }
                i++;
            }
        }
    }
    
    /**
     * Geeft het stuk terug met de opgegeven eigenschappen
     * @param dark
     * @param big
     * @param circle
     * @param hole
     * @return piece
     */
    public Piece getPiece(String dark, String big, String circle, String hole)
    {
        Piece piece = null;
        
        int i = 0;
        while (i < this.pieces.size() && piece == null)
        {
            if (this.pieces.get(i).compare(new Piece(dark, big, circle, hole)))
            {
                piece = this.pieces.get(i);
            }
            
            i++;
        }
        
        return piece;
    }
}
